import math
from vec import Vec4
from vec import Vec3
from matrix import Matrix4x4
import time

WIDTH = 500
HEIGHT = 500
BACK_COLOR = (0, 0, 0)
spd = 1
lastTime = time.time()
deltaTime = 0

rot_x = Matrix4x4()
rot_x.set_matrix([
    [1, 0, 0, 0],
    [0, math.cos(spd / 180 * math.pi), -math.sin(spd / 180 * math.pi), 0],
    [0, math.sin(spd / 180 * math.pi), math.cos(spd / 180 * math.pi), 0],
    [0, 0, 0, 1]
])
rot_y = Matrix4x4()
rot_y.set_matrix([
    [math.cos(spd / 180 * math.pi), 0, math.sin(spd / 180 * math.pi), 0],
    [0, 1, 0, 0],
    [-math.sin(spd / 180 * math.pi), 0, math.cos(spd / 180 * math.pi), 0],
    [0, 0, 0, 1]
])

rot_z = Matrix4x4()
rot_z.set_matrix([
    [math.cos(spd / 180 * math.pi), -math.sin(spd / 180 * math.pi), 0, 0],
    [math.sin(spd / 180 * math.pi), math.cos(spd / 180 * math.pi), 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1]
])

class Cube:
    def __init__(self, pos, size):
        self.pos = pos
        self.size = size
        hs = self.size / 2
        p = self.pos
        self.vertices = []
        self.vertices.append(Vec3(p.x - hs, p.y - hs, p.z - hs))
        self.vertices.append(Vec3(p.x - hs, p.y - hs, p.z + hs))
        self.vertices.append(Vec3(p.x + hs, p.y - hs, p.z + hs))
        self.vertices.append(Vec3(p.x + hs, p.y - hs, p.z - hs))
        self.vertices.append(Vec3(p.x - hs, p.y + hs, p.z - hs))
        self.vertices.append(Vec3(p.x - hs, p.y + hs, p.z + hs))
        self.vertices.append(Vec3(p.x + hs, p.y + hs, p.z + hs))
        self.vertices.append(Vec3(p.x + hs, p.y + hs, p.z - hs))
        self.edges = []
        self.edges.append((0, 1))
        self.edges.append((1, 2))
        self.edges.append((2, 3))
        self.edges.append((3, 0))
        self.edges.append((4, 5))
        self.edges.append((5, 6))
        self.edges.append((6, 7))
        self.edges.append((7, 4))
        self.edges.append((0, 4))
        self.edges.append((1, 5))
        self.edges.append((2, 6))
        self.edges.append((3, 7))

        self.update_local_axis()

        self.face_to_edges = {}
        self.update_face_vec()

        self.face_to_vertices = {}
        self.face_to_vertices['up'] = [4, 5, 6, 7]
        self.face_to_vertices['down'] = [0, 1, 2, 3]
        self.face_to_vertices['left'] = [0, 3, 7, 4]
        self.face_to_vertices['right'] = [1, 2, 6, 5]
        self.face_to_vertices['front'] = [2, 3, 7, 6]
        self.face_to_vertices['back'] = [0, 1, 5, 4]

    def update_face_vec(self):
        self.set_face_vec('up', 7, 4, 5)
        self.set_face_vec('down', 1, 0, 3)
        self.set_face_vec('left', 3, 0, 4)
        self.set_face_vec('right', 5, 1, 2)
        self.set_face_vec('front', 2, 3, 7)
        self.set_face_vec('back', 4, 0, 1)

    def set_face_vec(self, face, a_idx, o_idx, b_idx):
        a = self.vertices[a_idx] - self.vertices[o_idx]
        a.normalize()
        b = self.vertices[b_idx] - self.vertices[o_idx]
        b.normalize()
        self.face_to_edges[face] = [a, b]

    def get_local_vertices(self):
        local_vertices = []
        for item in self.vertices:
            local_vertices.append(item - self.pos)

        return local_vertices

    def update_local_axis(self):
        a = self.vertices[5] - self.vertices[1]
        b = self.vertices[2] - self.vertices[1]
        c = a.cross(b)
        c.normalize()
        self.local_x = c

        a = self.vertices[4] - self.vertices[5]
        b = self.vertices[6] - self.vertices[5]
        c = a.cross(b)
        c.normalize()
        self.local_y = c

        a = self.vertices[2] - self.vertices[3]
        b = self.vertices[7] - self.vertices[3]
        c = a.cross(b)
        c.normalize()
        self.local_z = c

        self.local_x.output()
        self.local_y.output()
        self.local_z.output()
        
    def get_vertices(self):
        return self.vertices
    def update_vertices(self, v):
        for i in range(len(self.vertices)):
            self.vertices[i] = v[i]
        self.update_face_vec()

    def which_faces_belong_to(self, v_index):
        faces = []
        for k, v in self.face_to_vertices.items():
            if v_index in v:
                faces.append(k)
        return faces

    def get_face_normal(self, face):
        edges = self.face_to_edges[face]
        return edges[0].cross(edges[1])

    def render_cube(self):
        vec = cube.get_vertices()
        local_vec = cube.get_local_vertices()
        vectices = []
        new_vertices = []
        for item in local_vec:
            v = item.to_vec4(1)
            v = rot_x.mul_vec4(v)
            v = rot_y.mul_vec4(v)
            v = rot_z.mul_vec4(v)
            wv = self.pos + v.to_vec3()
            new_vertices.append(wv)
            v = wv.to_vec4(1)
            a = om.mul_vec4(v)
            # a = pm.mul_vec4(v)
            a = a / a.w
            vectices.append(a)

        cube.update_vertices(new_vertices)
        forward = Vec3(0, 0, 1)
        
        
        for i in range(len(self.edges)):
            is_edge_visible = True
            cur_edge = self.edges[i]
            for edge_vertex_index in cur_edge:
                faces = self.which_faces_belong_to(edge_vertex_index)
                is_visible = False
                for f in faces:
                    fn = self.get_face_normal(f)
                    if fn.dot(forward) < 0:
                        is_visible = True
                        break
                if is_visible == False:
                    is_edge_visible = False
                    break

            if is_edge_visible == True:
                f = (250 + vectices[cur_edge[0]].x * 50, 250 + vectices[cur_edge[0]].y * 50)
                t = (250 + vectices[cur_edge[1]].x * 50, 250 + vectices[cur_edge[1]].y * 50)
                
                screen.draw.line(f, t, (255, 255, 255))
            
def create_ortho_matrix(w, h, n, f):
    o = Matrix4x4()
    o.set_matrix([
        [2 / w, 0, 0, 0],
        [0, 2 / h, 0, 0],
        [0, 0, 1 / (f - n), -n / (f - n)],
        [0, 0, 0, 1]
    ])

    return o

def create_perspective_matrix(fov, r, n, f):
    p = Matrix4x4()

    p.set_matrix([
        [1/r * 1 / math.tan(fov / 2), 0, 0, 0],
        [0, math.tan(fov / 2), 0, 0],
        [0, 0, f / (f - n), -f * n / (f - n)],
        [0, 0, 1, 0]
    ])

    return p

cube = Cube(Vec3(0, 0, 20), 10)

om = create_ortho_matrix(-10, 10, 0.1, 100)
pm = create_perspective_matrix(math.pi / 3, 4 / 3, 0.1, 100)

def update():
    global lastTime, deltaTime
    deltaTime = time.time() - lastTime
    lastTime = time.time()

def draw():
    global cube, result
    screen.clear()
    screen.fill(BACK_COLOR)
    cube.render_cube()
    